{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Advanced analysis of estimates\n",
    "\n",
    "👋 Hello, this sample will showcase how to perform resource estimation\n",
    "experiments created on top of Azure Quantum Resource Estimator.  The sample will\n",
    "re-use the quantum multiplication algorithm from the _Estimates with Q#_\n",
    "notebook. We leverage the `azure_quantum` API to configure and submit resource\n",
    "estimation jobs.\n",
    "\n",
    "## Setup\n",
    "\n",
    "Let's import some packages and connect to the Azure Quantum workspace."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from azure.quantum import Workspace\n",
    "from azure.quantum.target.microsoft import MicrosoftEstimator, QubitParams\n",
    "from azure.quantum.target.microsoft.target import MicrosoftEstimatorQubitParams\n",
    "import numpy as np                         # To store experimental data from job results\n",
    "from matplotlib import pyplot as plt       # To plot experimental results\n",
    "from matplotlib.colors import hsv_to_rgb   # To automatically find colors for plots"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "workspace = Workspace(\n",
    "    resource_id = \"\",\n",
    "    location = \"\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We are also importing the `qsharp` Python package to write Q# code in Jupyter\n",
    "cells, and import the `Microsoft.Quantum.Numerics` package that contains the\n",
    "multiplication algorithm."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import qsharp\n",
    "qsharp.packages.add(\"Microsoft.Quantum.Numerics\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Implementing the algorithm\n",
    "\n",
    "As running example algorithm we are creating a multiplier using the [MultiplyI](https://docs.microsoft.com/qsharp/api/qsharp/microsoft.quantum.arithmetic.multiplyi) operation.  We can configure the size of the multiplier with a bitwidth parameter. The operation will have two input registers with that bitwidth, and one output register with the size of twice the bitwidth."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "EstimateMultiplication: any = None # Make Python recognize the Q# function (optional)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%qsharp\n",
    "\n",
    "open Microsoft.Quantum.Arithmetic;\n",
    "\n",
    "operation EstimateMultiplication(bitwidth : Int) : Unit {\n",
    "    use factor1 = Qubit[bitwidth];\n",
    "    use factor2 = Qubit[bitwidth];\n",
    "    use product = Qubit[2 * bitwidth];\n",
    "\n",
    "    MultiplyI(LittleEndian(factor1), LittleEndian(factor2), LittleEndian(product));\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setting up and running the experiments\n",
    "\n",
    "Next, we are setting up some experiments. Here, we are using two of the six\n",
    "pre-defined qubit parameter models, and one customized model based on the model\n",
    "`qubit_gate_ns_e3` (accessed via the constant `QubitParams.GATE_NS_E3`), in\n",
    "which we change the error rates to $10^{-3.5} \\approx 0.00032$. In your own\n",
    "experiments, you can change the number of items, and also the parameters. You\n",
    "may use other pre-defined models or define custom models. You can find more\n",
    "information about the input parameters in the _Getting Started with Azure\n",
    "Quantum Resource Estimation_ notebook.  Note that in `target_params` we have\n",
    "tuples of names and qubit parameters.  We are using the names for the plots when\n",
    "analyzing the results.\n",
    "\n",
    "Further, we are choosing a list of input parameters to our algorithm, in this\n",
    "case bitwidths that are powers-of-2 ranging from 8 to 64."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "estimator = MicrosoftEstimator(workspace=workspace)\n",
    "\n",
    "target_params = [\n",
    "    (\"Gate-based ns, 10⁻³\", MicrosoftEstimatorQubitParams(name=QubitParams.GATE_NS_E3)),\n",
    "    (\"Gate-based ns, 10⁻³ᐧ⁵\", MicrosoftEstimatorQubitParams(name=QubitParams.GATE_NS_E3, one_qubit_measurement_error_rate=0.00032, one_qubit_gate_error_rate=0.00032, two_qubit_gate_error_rate=0.00032, t_gate_error_rate=0.00032)),\n",
    "    (\"Gate-based ns, 10⁻⁴\", MicrosoftEstimatorQubitParams(name=QubitParams.GATE_NS_E4))\n",
    "]\n",
    "bitwidths = [8, 16, 32, 64]\n",
    "\n",
    "# This is to access the names of the target parameters\n",
    "names = [name for (name, _) in target_params]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We are now creating a batching job, in which we create all the combination of\n",
    "target_params and bitwidths.  The bitwidth is assigned by accessing it through\n",
    "the `arguments` field of the item.  We then submit the job with these items for\n",
    "the multiplication algorithm and wait for the results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "params = estimator.make_params(num_items=len(bitwidths) * len(target_params))\n",
    "\n",
    "for i, (_, target_param) in enumerate(target_params):\n",
    "    for j, bitwidth in enumerate(bitwidths):\n",
    "        params.items[i * len(bitwidths) + j].qubit_params = target_param\n",
    "        params.items[i * len(bitwidths) + j].arguments[\"bitwidth\"] = bitwidth\n",
    "\n",
    "job = estimator.submit(EstimateMultiplication, input_params=params)\n",
    "results = job.get_results()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Plotting the experimental results\n",
    "\n",
    "Now that we have computed the results, we extract some data from it.  We extract the\n",
    "number of physical qubits, the total runtime in nanoseconds, and the QEC code\n",
    "distance for the logical qubits.  In addition to the total number of physical\n",
    "qubits, we are also extracting their breakdown into number of physical qubits\n",
    "for executing the algorithm and the number of physical qubits required for the T\n",
    "factories that produce the required T states."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "qubits = np.zeros((len(names), len(bitwidths), 3))\n",
    "runtime = np.zeros((len(names), len(bitwidths)))\n",
    "distances = np.zeros((len(names), len(bitwidths)))\n",
    "\n",
    "for bitwidth_index, bitwidth in enumerate(bitwidths):\n",
    "    for name_index, name in enumerate(names):\n",
    "        # Note that the results are ordered by target parameters first, then by bitwidth\n",
    "        data = results.data(name_index * len(bitwidths) + bitwidth_index)\n",
    "\n",
    "        qubits[(name_index, bitwidth_index, 0)] = data['physicalCounts']['physicalQubits']\n",
    "        qubits[(name_index, bitwidth_index, 1)] = data['physicalCounts']['breakdown']['physicalQubitsForAlgorithm']\n",
    "        qubits[(name_index, bitwidth_index, 2)] = data['physicalCounts']['breakdown']['physicalQubitsForTfactories']\n",
    "\n",
    "        runtime[(name_index, bitwidth_index)] = data['physicalCounts']['runtime']\n",
    "\n",
    "        distances[(name_index, bitwidth_index)] = data['logicalQubit']['codeDistance']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, we are using [Matplotlib](https://matplotlib.org/) to plot the number\n",
    "of physical qubits and the runtime as bar plots, and the QEC code distances as a\n",
    "scatter plot.  For the physical qubits, we are showing the partition into qubits\n",
    "required for the algorithm and qubits required for the T factories."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axs = plt.subplots(1, 3, figsize=(22, 6))\n",
    "\n",
    "num_experiments = len(names)                         # Extract number of experiments form names (can be made smaller)\n",
    "xs = np.arange(0, len(bitwidths))                    # Map bitwidths to numeric indexes for plotting\n",
    "full_width = .8                                      # Total width of all bars (should be smaller than 1)\n",
    "width = full_width / num_experiments                 # Fractional width of a single bar\n",
    "xs_left = xs - (((num_experiments - 1) * width) / 2) # Starting x-coordinate for bars\n",
    "\n",
    "# Split axes into qubit and runtime plots\n",
    "ax_qubits, ax_runtime, ax_code_distance = axs\n",
    "\n",
    "# Plot physical qubits\n",
    "for i in range(num_experiments):\n",
    "    ax_qubits.bar(xs_left + i * width, qubits[i,:,1], width, label=f\"{names[i]} (Alg.)\", color=hsv_to_rgb((i / num_experiments, 1.0, .8)))\n",
    "    ax_qubits.bar(xs_left + i * width, qubits[i,:,2], width, bottom=qubits[i,:,1], label=f\"{names[i]} (T fac.)\", color=hsv_to_rgb((i / num_experiments, 0.3, .8)))\n",
    "ax_qubits.set_title(\"#Physical qubits\")\n",
    "ax_qubits.set_xlabel(\"Bitwidth\")\n",
    "ax_qubits.set_xticks(xs)\n",
    "ax_qubits.set_xticklabels(bitwidths)\n",
    "ax_qubits.legend()\n",
    "\n",
    "# Plot runtime\n",
    "for i in range(num_experiments):\n",
    "    ax_runtime.bar(xs_left + i * width, np.array(runtime[i,:]) / 1e6, width, label=names[i], color=hsv_to_rgb((i / num_experiments, 1.0, .8)))\n",
    "ax_runtime.set_title(\"Runtime (ms)\")\n",
    "ax_runtime.set_xlabel(\"Bitwidth\")\n",
    "ax_runtime.set_xticks(xs)\n",
    "ax_runtime.set_xticklabels(bitwidths)\n",
    "ax_runtime.legend()\n",
    "\n",
    "# Plot code distances\n",
    "for i in range(num_experiments):\n",
    "    ax_code_distance.scatter(xs, distances[i,:], label=names[i], marker='*', color=hsv_to_rgb((i / num_experiments, 1.0, 0.8)))\n",
    "ax_code_distance.set_title(\"QEC code distance\")\n",
    "ax_code_distance.set_xlabel(\"Bitwidth\")\n",
    "ax_code_distance.set_xticks(xs)\n",
    "ax_code_distance.set_xticklabels(bitwidths)\n",
    "ax_code_distance.legend()\n",
    "\n",
    "fig.suptitle(\"Resource estimates for multiplication\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Accessing the results table\n",
    "\n",
    "Next, we are showing all estimation results for the first bitwidth in a\n",
    "side-by-side table.  Notice that the items are ordered by target parameters\n",
    "first, then by bitwidths.  Therefore, all items with bitwidth 8 are at indices\n",
    "0, 4, and 8, where the step size 4 corresponds to the number of different\n",
    "bitwidths.\n",
    "\n",
    "You can also access individual results by providing a number as index, e.g.,\n",
    "`results[1]` to show the results table of the configuration with the first set\n",
    "of target parameters and bitwidth 16."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "bitwidth_index = 0\n",
    "results[bitwidth_index::len(bitwidths)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Further, we can plot all items in the result object using the `plot()` function.\n",
    "That function takes as optional parameter an array of labels for the plot's\n",
    "legend.  We are deriving the labels from the names and bitwidths, similar to how\n",
    "the items were created."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "results.plot(labels=[f\"{name} ({bitwidth} bit)\" for name in names for bitwidth in bitwidths])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Next steps\n",
    "\n",
    "We hope you got some ideas and inspirations for your own resource estimation\n",
    "experiments.  Feel free to use this notebook as a starting point for your own\n",
    "algorithm investigations.  To get more familiar with resource estimation\n",
    "experiments, here are some suggestions to try out in this notebook:\n",
    "\n",
    "* Add a fourth plot to show some statistics about a single T factory, e.g., its\n",
    "  number of qubits.\n",
    "* Add a new plot series to show logical resource estimates.\n",
    "* Change the algorithm to create an $n$-ary multiplier (with a variable number\n",
    "  of input arguments) for either a fixed or customizable bitwidth.\n",
    "* Create a side-by-side comparison table for a fixed qubit parameter comparing all bitwidths."
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
